// Copyright (c) 2008 Matthew Johnson
// Licensed under the MIT License (see license.txt)
package actionml.display {
	import actionml.Global;

	import flash.display.DisplayObject;	
	import flash.display.DisplayObjectContainer;	
	import flash.display.MovieClip;
	import flash.filters.BitmapFilter;
	import flash.text.TextField;
		
	public class MovieClipMethods {
		public static function initialize() {
			Global.initializeExtension(MovieClipMethods, MovieClip);
		}
		public static var staticMethods:Object = {
			fromXML: function(xml:XML, stop:Boolean = true, root:DisplayObjectContainer = null) {
				root ||= Global.document;
				var t:Object = MovieClipMethods["getTracer"]({method:"fromXML", context:"xml"});
				var o:DisplayObject;
				var clipClass:Class;
				if (Object(xml).attribute("instanceName").length() == 1) {
					o = root.getChildByName(xml.@instanceName);
					if (!o) {
						t.error("The instance name '" + xml.@instanceName + "' does not represent a valid child instance of " + root + ".  This xml appears to be incorrect:\n\t" + Object(xml).toXMLString());
					}
				} else if (Object(xml).attribute("symbol").length() == 1) {
					clipClass = Object(Class).forName(xml.@symbol);
					if (clipClass) {
						o = new clipClass();
						root.addChild(o);
					} else {
						t.error("The symbol name '" + xml.@symbol + "' does not represent a valid symbol.  This xml appears to be incorrect:\n\t" + Object(xml).toXMLString());
					}										
				} else {
					o = root.getChildByName(Object(xml).localName());
					if (o == null) {
						clipClass = Object(Class).forName(Object(xml).localName());
						if (clipClass) {
							o = new clipClass();
							root.addChild(o);
						}
					}
				}
				if (o != null) {
					o.configureFromXML(xml, stop);
				} else {
					t.error("This xml should contain either an instanceName or a symbol attribute or the element name should match an instance name or symbol in your fla file.  It appears to be incorrect:\n\t" + Object(xml).toXMLString())
				}
				return o;
			}
		};
		public static var instanceMethods:Object = {
			playMedia: function() {
				this.playing = true;
				if (this.currentFrame == this.totalFrames) {
					this.gotoAndPlay(0);
				} else {
					this.play();
				}
				this.doEvent("play");
			},
			stopMedia: function() {
				this.playing = false;
				this.stop();
				this.doEvent("pause");
			},
			getBeginningLocation: function() {
				return 1;
			},
			getEndingLocation: function() {
				return this.totalFrames;
			},
			getLocation: function() {
				return this.currentFrame;
			},
			setLocation: function(l:int) {
				if (this.playing) {
					this.gotoAndPlay(l);
				} else {
					this.gotoAndStop(l);
				}
			},
			loadChildrenFromXML: function(xml:XML, stop:Boolean = true):MovieClip {
				var loaderMap:Object = {
					"tweens": "loadTweensFromXML",
					"expressions": "loadExpressionsFromXML",
					"actions": "loadActionsFromXML"
				};
				var t:Object = MovieClipMethods["getTracer"]({method:"loadChildrenFromXML", context:"xml"});
				for each (var childXml:XML in Object(xml).elements()) {
					if (Object(childXml).localName() in loaderMap) {		
						this[loaderMap[Object(childXml).localName()]](childXml)
					} else {
						var child:DisplayObject = MovieClip["fromXML"](childXml, stop, this);
						if (child == null) {
							t.warn("No child of " + this + " could be loaded from the following xml:\n\t" + Object(childXml).elements());
							this[Object(childXml).localName()] = Object(childXml).toString().replace(new RegExp("^<" + Object(childXml).localName() + ">"), "").replace(new RegExp("</" + Object(childXml).localName() + ">$"), "");
						}
					}
				}
			},
			loadTweensFromXML: function(xml:XML) {
				for each (var tweenXml:XML in Object(xml).elements()) {
					this.tweens ||= {};
					this.tweens[tweenXml.localName()] = this.loadTweenFromXml(tweenXml);
				}
			},
			loadTweenFromXml: function(xml:XML) {
				var tween:* = {name: Object(xml).localName(), target: this};
				var tweenAttributes:* = Object(xml).getAttributes();
				for each (var a in tweenAttributes.getPropertyNames()) {
					if (a == "playWhenFinished") {
						tween.playWhenFinished ||= [];
						for each (var t:String in tweenAttributes[a].split(/\s*,\s*/)) {
							tween.playWhenFinished.push(t);
						}
					} else {
						tween[a] = tweenAttributes[a];
					}
				}
				if (xml.playWhenFinished.length() > 0) {
					for each (var whenFinishedXml:XML in xml.playWhenFinished["elements"]()) {
						tween.playWhenFinished ||= [];
						tween.playWhenFinished.push(arguments.callee(whenFinishedXml));
					}
				}
				return new (Class["forName"]("actionml.animation.Tween"))(tween);  // precompile
			},
			loadExpressionsFromXML: function(xml:XML) {
				var attributes:Object = Object(xml).getAttributes();
				for (var expressionName:String in attributes) {
					var expression:Object = attributes[expressionName].compileExpression();
					this["get" + expressionName.toCapitalCase()] = function() {
						return this.evaluateExpression(expression);
					}
				}
			},
			loadActionsFromXML: function(xml:XML) {
				var o:DisplayObject = this;
				// include attribute actions
				var actionAttributes:Object = Object(xml).getAttributes();
				for (var attribute:String in actionAttributes) {
					var actions:Array = actionAttributes[attribute].split(/\s*,\s*/);
					var actionName = attribute;
					if (actionName["startsWith"]("on")) {
						actionName = actionName + "Handler";
					}
					o[attribute] = function(o:Object, event:String) {
						for each (var a:String in actions) {
							this.doAction(a, event);
						}
					}
				}
				// include element actions
				for each (var actionXml:XML in Object(xml).elements()) {
					loadActionFromXML(actionXml);
				}
				function loadActionFromXML(actionXml:XML) {
					var statements:Array = [];
					for each (var statementXml:XML in Object(actionXml).elements()) {
						var localName:String = statementXml.localName();
						var attributes:Object = Object(statementXml).getAttributes();
						if (localName == "do") {
							statements.push(attributes["actions"].split(/\s*,\s*/));
						} else if (localName == "assign") {
							statements.push(attributes);
						} else if (localName == "tween") {
							statements.push(o["loadTweenFromXml"](statementXml));
						} else {
							trace("Invalid action XML at " + statementXml.toXMLString());
						}
					}
					// the action is stored as a method using the
					// name of the action element as the method name
					// the method loops through the statements array
					// and executes action statements, plays tweens, 
					// and sets values for expressions
					var actionName = Object(actionXml).localName();
					if (actionName["startsWith"]("on")) {
						actionName = actionName + "Handler";
					}
					o[actionName] = function(o:Object, event:String) {
						for each (var s:Object in statements) {
							if (s is Array) {
								for each (var a:String in s) {
									this.doAction(a, event);
								}
							} else if (s.constructor.toString().endsWith("Tween")) {
								this.playTween(s);
							} else {
								for (var p:String in s) {
									this.setValueForKeypath(p, this.evaluateExpression(s[p]));
								}
							}
						}
					};
				}
			}
		};
	}
}